package com.heima.wemedia.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.common.constants.message.ArticleForEsConstants;
import com.heima.common.constants.message.NewsAutoScanConstants;
import com.heima.common.constants.message.WmNewsMessageConstants;
import com.heima.common.constants.wemedia.WemediaConstants;
import com.heima.common.exception.CustomException;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.threadlocal.WmThreadLocalUtils;
import com.heima.model.wemedia.dto.NewsAuthDto;
import com.heima.model.wemedia.dto.WmNewsPageReqDto;
import com.heima.model.wemedia.pojo.*;
import com.heima.model.wemedia.vo.WmNewsVo;
import com.heima.wemedia.mapper.WmMaterialMapper;
import com.heima.wemedia.mapper.WmNewsMapper;
import com.heima.wemedia.mapper.WmNewsMaterialMapper;
import com.heima.wemedia.mapper.WmUserMapper;
import com.heima.wemedia.service.WmNewsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import springfox.documentation.spring.web.json.Json;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class WmNewsServiceImpl extends ServiceImpl<WmNewsMapper, WmNews>implements WmNewsService {

    @Value("${file.oss.web-site}")
    String website;

    @Autowired
    private WmNewsMaterialMapper wmNewsMaterialMapper;//为什么加private,私有变量,本类调用,安全

    @Override
    public ResponseResult findAll(WmNewsPageReqDto dto) {
        //1.检查参数

        dto.checkParam();//查看分页参数
        //用户是否登录
        WmUser wmUser = WmThreadLocalUtils.getUser();
        if (wmUser==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
        }
        //2.封装查询条件


        //2.1 分页的条件
        IPage<WmNews> pageReq=new Page<>(dto.getPage(),dto.getSize());

        LambdaQueryWrapper<WmNews> queryWrapper = Wrappers.<WmNews>lambdaQuery();

        //2.2 查看状态条件是否存在

        if (dto.getStatus()!=null){
            queryWrapper.eq(WmNews::getStatus,dto.getStatus());
        }

        //查看关键字是否存在

        if (StringUtils.isNotBlank(dto.getKeyword())){
            queryWrapper.like(WmNews::getTitle,dto.getKeyword());
        }

        //查看频道id是否存在

        if (dto.getChannelId()!=null){

            queryWrapper.eq(WmNews::getChannelId,dto.getChannelId());
        }


        //发布日期

        if (dto.getBeginPubdate()!=null&&dto.getEndPubdate()!=null){

            queryWrapper.between(WmNews::getPublishTime,dto.getBeginPubdate(),dto.getEndPubdate());
        }


        //登录用户的id,只能查看本用户的

        if (wmUser.getId()!=null){
            queryWrapper.eq(WmNews::getUserId,wmUser.getId());
        }


        queryWrapper.orderByDesc(WmNews::getPublishTime);

        IPage<WmNews> pageResult = this.page(pageReq, queryWrapper);

        PageResponseResult pageResponseResult = new PageResponseResult(dto.getPage(),dto.getSize(),pageResult.getTotal());

        pageResponseResult.setData(pageResult.getRecords());

        //分页查询,返回结果添加图片访问前缀
        pageResponseResult.setHost(website);//完整的访问oss路径

        return pageResponseResult;
    }
    @Transactional//加事务,支持回滚,保存的方法,
    @Override//发表文章    发送自动审核的起始点
    public ResponseResult submitNews(WmNewsDto dto) {

        //检测参数  封装WmNews对象
        //1.检测文章内容
        if (dto==null||StringUtils.isBlank(dto.getContent())){

            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID);
        }
        //2.文章的状态
        Short status = dto.getStatus();//包装类   之间相等用equals
        if (!status.equals(WemediaConstants.WM_NEWS_DRAFT_STATUS)&&!status.equals(WemediaConstants.WM_NEWS_SUMMIT_STATUS)){
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID,"文章状态参数错误");
        }

        //是否登录
        WmUser user = WmThreadLocalUtils.getUser();
        if (user==null){
            throw new CustomException(AppHttpCodeEnum.NEED_LOGIN);
        }
        //基于dto封装wmNews
        WmNews wmNews = new WmNews();

        BeanUtils.copyProperties(dto,wmNews);
        //如果type为自动生成,暂时设置为null,因为表中字段设置为了无符号,值不能负数
        if (dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)){
            wmNews.setType(null);//封面图片选择
        }

        //如果images集合不为null,拼接成字符串,并替换前缀(原oss)
        List<String> images = dto.getImages();
        if (images!=null&&images.size()>0){
            //封装方法
            String str = imagesListToStr(images);
            wmNews.setImages(str);
        }

        //2.保存或修改WmNews对象

        saveOrUpdateWmNews(wmNews);

        //3.保存文章和素材的关联关系

        //3.1 抽取文章内容中涉及的图片   字符串数组转化为map类型的集合
        List<String> contentImages=parseContentImages(dto.getContent());

        //3.2 保存文章内容中引用的图片素材的关联关系
        if (status.equals(WemediaConstants.WM_NEWS_SUMMIT_STATUS)&&contentImages!=null&&contentImages.size()>0){
            saveRelativeInfoForContent(contentImages,wmNews);
        }
        //3.3 保存  文章封面所引用的图片素材的关联关系
        //方法  自上而下执行
        if (status.equals(WemediaConstants.WM_NEWS_SUMMIT_STATUS)){
            saveRelativeInfoForCover(contentImages,wmNews,dto);

            //只有待审核的状态  才能调用
            //调用kafda  自动审核的方法
            kafkaTemplate.send(NewsAutoScanConstants.WM_NEWS_AUTO_SCAN_TOPIC,String.valueOf(wmNews.getId()));//向哪个主题发送消息

            log.info("发表文章成功,并通过kafka通知admin 审核===>{}",wmNews.getId());

        }

        //如果type类型为-1,需要从文章内容图片中生成封面



        return ResponseResult.okResult();


    }

    @Autowired
    KafkaTemplate<String,String> kafkaTemplate;//消息都是string  类型


    /**
     *  根据ID查询自媒体文章  一个回显的功能
     * @param id
     * @return
     */
    @Override
    public ResponseResult findWmNewsById(Integer id) {
        if(id == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"文章ID不能为空");
        }
        WmNews wmNews = getById(id);
        if(wmNews == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"该文章不存在");
        }
        ResponseResult responseResult = ResponseResult.okResult(wmNews);
        responseResult.setHost(website);//注意更换前缀,前端可以访问,wmNews中image没有访问前缀
        return responseResult;
    }

    //("根据ID删除自媒体文章")
    @Transactional
    @Override
    public ResponseResult delNews(Integer id) {
        //1.检查参数(检查是否登录)
        if (id==null){
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID,"文章id不能为空");
        }
        WmUser user = WmThreadLocalUtils.getUser();
        if (user==null){
            throw new CustomException(AppHttpCodeEnum.NEED_LOGIN);
        }


        //根据id查询自媒体文章
        WmNews wmNews = getById(id);
        if (wmNews==null){
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST,"文章不存在");
        }

        //判断文章状态是否为9 并且是否为上架状态
        if (wmNews.getStatus().equals(WemediaConstants.WM_NEWS_PUBLISH_STATUS)&&wmNews.getEnable().equals(WemediaConstants.WM_NEWS_ENABLE_UP)){
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_ALLOW,"发布且上架状态的文章不可以删除");
        }

        if (!wmNews.getUserId().equals(user.getId())){
            throw new CustomException(AppHttpCodeEnum.NO_OPERATOR_AUTH,"不是本人,无权操作");
        }
        //删除文章数据
        removeById(id);
        //删除文章  和 素材  关联的数据   多步操作   用事务
        wmNewsMaterialMapper.delete(Wrappers.<WmNewsMaterial>lambdaQuery().eq(WmNewsMaterial::getNewsId,id));
        return ResponseResult.okResult();
    }

    /**
     * 上架和下架
     * @param dto
     * @return
     */
    @Override
    public ResponseResult downOrUp(WmNewsDto dto) {//上下架
        // 1. 检查参数  (id    enable = 0 或 1)
        if(dto.getId()==null){
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID,"文章id不能为空");
        }
        if(!WemediaConstants.WM_NEWS_ENABLE_UP.equals(dto.getEnable())
                &&!WemediaConstants.WM_NEWS_ENABLE_DOWN.equals(dto.getEnable())){
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID,"文章上下状态错误");
        }
        // 2. 根据id查询文章
        WmNews wmNews = getById(dto.getId());
        if(wmNews==null){
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST,"文章不存在");
        }
        // 3. 检查status 是否为9,发布状态,只有发布状态才能进行上下架
        if(!wmNews.getStatus().equals(WemediaConstants.WM_NEWS_PUBLISH_STATUS)){
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_ALLOW,"只有发布状态的文章才可以上下架");
        }
        // 4. 修改enable 字符    下架后修改status   有发布状态转为待审核阶段
        LambdaUpdateWrapper<WmNews> updateWrapper = Wrappers.<WmNews>lambdaUpdate();//创建条件,只更改字段的值,只修改字段的值,属性没回填

        updateWrapper.set(WmNews::getEnable,dto.getEnable());//update  set
        if(dto.getEnable().equals(WemediaConstants.WM_NEWS_ENABLE_DOWN)){//如果传入的参数时下架
            updateWrapper.set(WmNews::getStatus,WemediaConstants.WM_NEWS_SUMMIT_STATUS);//更改状态,为1,待审核
        }
        updateWrapper.eq(WmNews::getId,dto.getId()).eq(WmNews::getUserId,WmThreadLocalUtils.getUser().getId());//链式编写,增加条件,
        update(
                updateWrapper//update自带的方法
        );


        //自媒体端远程更改app端文章的状态
        //消息的生产者

        //判断文章有没有关联app端的文章
        if (wmNews.getArticleId()!=null){
            //传递的消息   一个是文章的id    上下架的信息
            Map map = new HashMap();
            map.put("articleId",wmNews.getArticleId());
            map.put("enable",dto.getEnable());//没有进行回填
            kafkaTemplate.send(WmNewsMessageConstants.WM_NEWS_UP_OR_DOWN_TOPIC, JSON.toJSONString(map));
            log.info("自动同步文章上架功能启动,通知article消息,发送成功");
            //同时通知搜索微服务
            //是否上架  0 下架  1 上架
            map.put("articleId",wmNews.getArticleId());
            if(dto.getEnable().intValue() == 0){//getEnable  即可以上架也可以下架
                kafkaTemplate.send(ArticleForEsConstants.ARTICLE_ELASTICSEARCH_DELETE,JSON.toJSONString(map));//发送给主题
            }else {
                kafkaTemplate.send(ArticleForEsConstants.ARTICLE_ELASTICSEARCH_ADD,JSON.toJSONString(map));
            }
            log.info("文章上下架，同步通知search微服务消息 发送成功===================================");


        }
        return ResponseResult.okResult();
    }

    /**
     * 查询待发布的文章
     * @return
     */
    @Override
    public List<Integer> findRelease() {
        //1.状态为4或8   发布时间要小于当前时间
        //只取id字段的值   select     in   范围    le 小于  select  只取单个字段
        List<WmNews> wmNewsList = this.list(Wrappers.<WmNews>lambdaQuery().in(WmNews::getStatus, 4, 8).le(WmNews::getPublishTime,new Date()).select(WmNews::getId));
        //得到文章的id  对集合处理用流   map  取值
        List<Integer> integerList = wmNewsList.stream().map(WmNews::getId).collect(Collectors.toList());
        return integerList;
    }


    @Autowired
    WmNewsMapper wmNewsMapper;
    //查询待人工审核列表
    @Override
    public ResponseResult findList(NewsAuthDto dto) {
        //判断参数
        dto.checkParam();
        //封装查询条件
        //分页参数
        Integer currentPage = dto.getPage();//当前页数,不满足分页时查询的条件,是偏移量
        dto.setPage((currentPage-1)*dto.getSize());//偏移量


        if (StringUtils.isNotBlank(dto.getTitle())){
            dto.setTitle("%"+dto.getTitle()+"%");
        }
        //调用mapper查询    mabatisplus不支持多表查询   自定义xml

        List<WmNewsVo> listAndPage = wmNewsMapper.findListAndPage(dto);
        long total = wmNewsMapper.findListCount(dto);
        PageResponseResult pageResponseResult = new PageResponseResult(currentPage, dto.getSize(), total);
        pageResponseResult.setData(listAndPage);
        pageResponseResult.setHost(website);
        return pageResponseResult;

    }

    /**
     * 根据文章id  查询vo对象   文章内容
     * @param id
     * @return
     */
    //多表查询
    @Autowired
    WmUserMapper wmUserMapper;

    @Override
    public ResponseResult findWmNewsVo(Integer id) {
        WmNews wmNews = this.getById(id);
        if (wmNews==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"未发现该文章信息");
        }

        WmUser wmUser = wmUserMapper.selectById(wmNews.getUserId());

        if (wmUser==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"文章关联的作者信息不存在");

        }
        //封装vo对象    new 对象   拷贝属性

        WmNewsVo wmNewsVo = new WmNewsVo();
        BeanUtils.copyProperties(wmNews,wmNewsVo);
        wmNewsVo.setAuthorName(wmUser.getName());

        //封装返回结果
        ResponseResult responseResult = ResponseResult.okResult(wmNewsVo);
        responseResult.setHost(website);//查看图片

        return responseResult;
    }

    /**
     * 审核成功   修改文章状态   审核失败   修改文章  状态  给出失败的理由
     * @param code
     * @param dto
     * @return
     */
    @Override
    public ResponseResult updateStatus(Short code, NewsAuthDto dto) {
        //检测参数
        if (dto.getId()==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"文章id不存在");
        }
        WmNews wmNews = this.getById(dto.getId());
        if (wmNews==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"查询的文章不存在");
        }

        wmNews.setStatus(code);
        if (StringUtils.isNotBlank(dto.getMsg())){//字符串判断用String工具类
            wmNews.setReason(dto.getMsg());
        }
        //先set  具体更新的方法   用update
        this.updateById(wmNews);
        return ResponseResult.okResult();
    }

    /**
     * 保存  封面和素材的关联关系
     * @param contentImages  内容中的图片列表
     * @param wmNews   要保存的文章信息
     * @param dto  入参实体类
     */
    private void saveRelativeInfoForCover(List<String> contentImages, WmNews wmNews, WmNewsDto dto) {
        //根据dto的参数判断是否自动生成
        List<String> imagesList = dto.getImages();
        // 判断是否自动生成封面  //如果 type类型为-1 需要从文章内容图片中生成封面
        if (WemediaConstants.WM_NEWS_TYPE_AUTO.equals(dto.getType())) {
            if(contentImages!=null && contentImages.size()>0){
                int size = contentImages.size();
                // 1. 如果图片数量大于2  type=3 多图  取内容中前3张图片作为封面
                if(size>2){
                    imagesList = contentImages.stream().limit(3).collect(Collectors.toList());
                    wmNews.setType(WemediaConstants.WM_NEWS_MANY_IMAGE);
                }else if(size>0 && size <=2){
                    // 2. 如果图片数量大于0  小于2  type=1 单图  取内容中前1张图片作为封面
                    imagesList = contentImages.stream().limit(1).collect(Collectors.toList());
                    wmNews.setType(WemediaConstants.WM_NEWS_SINGLE_IMAGE);
                }
            }else {
                // 3. 其它情况 无图模式   type=0
                wmNews.setType(WemediaConstants.WM_NEWS_NONE_IMAGE);
            }
            // 将封面图片集合 转为逗号拼接的字符串
            wmNews.setImages(imagesListToStr(imagesList));  // 图片字符串
            updateById(wmNews);
        }
        if(imagesList!=null && imagesList.size()>0){
            // 确保替换掉路径前缀
            imagesList = imagesList.stream().map(url -> url.replaceAll(website,"")).collect(Collectors.toList());
            // 保存 封面 与 素材的关联关系
            saveRelativeInfo(imagesList,wmNews.getId(),WemediaConstants.WM_IMAGE_REFERENCE);
        }

    }

    private List<String> parseContentImages(String content) {

        //使用fastjson将文章内容转为list集合,集合中的每一个元素  都是一个map
        List<Map> contentImages = JSONArray.parseArray(content, Map.class);
        //对数据一系列操作用stream流操作
        List<String> contentImageList = contentImages.stream().filter(map -> WemediaConstants.WM_NEWS_TYPE_IMAGE.equals(map.get("type")))//过滤
                .map(map -> (String) map.get("value"))//映射处理,获取每个map中value的值
                .map(url -> url.replaceAll(website, ""))//变量随便定义,只要知道流里面的格式
                .collect(Collectors.toList());
        return contentImageList;
    }


    private void saveOrUpdateWmNews(WmNews wmNews) {
        wmNews.setCreatedTime(new Date());
        wmNews.setUserId(WmThreadLocalUtils.getUser().getId());
        wmNews.setSubmitedTime(new Date());
        wmNews.setEnable(WemediaConstants.WM_NEWS_ENABLE_UP);//上架,统一设置

        if (wmNews.getId()==null){
            save(wmNews);
        }else {
            //先删除再修改
            wmNewsMaterialMapper.delete(Wrappers.<WmNewsMaterial>lambdaQuery().eq(WmNewsMaterial::getNewsId, wmNews.getId()));
            updateById(wmNews);
        }

    }

    private String imagesListToStr(List<String> images) {
        String imageStr = images.stream().map(url -> url.replaceAll(website, "")).collect(Collectors.joining(","));
        return imageStr;
    }

    /**
     * 保存  内容中引用素材的关联关系
     * @param contentImages
     * @param wmNews
     */
    private void saveRelativeInfoForContent(List<String> contentImages,WmNews wmNews){

        saveRelativeInfo(contentImages,wmNews.getId(),WemediaConstants.WM_CONTENT_REFERENCE);



    }

    //提取出来一个公用的方法
    @Autowired
    WmMaterialMapper wmMaterialMapper;
    /**
     * 保存 文章 和素材的关联关系
     * @param materialUrls  素材的图片路径集合
     * @param newsId        文章的id
     *
     * @param type   引用的类型  0 内容引用  1  封面引用
     */
    public void saveRelativeInfo(List<String> materialUrls,Integer newsId,Short type){
        //根据素材的路径集合   及  登录用户的userId  查询素材集合
        List<WmMaterial> wmMaterials = wmMaterialMapper.selectList(Wrappers.<WmMaterial>lambdaQuery().eq(WmMaterial::getUserId, WmThreadLocalUtils.getUser().getId())
                .in(WmMaterial::getUrl, materialUrls));

        if (wmMaterials==null||wmMaterials.size()==0){

            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID,"引用的素材不存在");
        }

        //将素材列表转为map集合,   key:图片的url路径    value :素材的id
        Map<String, Integer> urlAndId = wmMaterials.stream().collect(Collectors.toMap(WmMaterial::getUrl, WmMaterial::getId));

        //查看引用的素材路径列表,在map中是否引用,如果有没有引用的,抛出异常
        List<Integer> materialIds = new ArrayList<>();
        materialUrls.forEach(url->{
            if (!urlAndId.containsKey(url)){
                throw new CustomException(AppHttpCodeEnum.PARAM_INVALID,"引用的素材不存在");
            }
            materialIds.add(urlAndId.get(url));
        });
        //收集所有素材ID组成一个集合
        wmNewsMaterialMapper.saveRelations(materialIds,newsId,type);

    }









}
